using System;
using UnityEngine;

namespace DanielSig.Geometry
{
	public struct Direction16 : IDirection, I16Bit
	{
		#region type casts
		public static explicit operator Direction16(Coordinates64 original)
		{
			return new Direction16(original);
		}
		public static explicit operator Direction16(Coordinates32 original)
		{
			return new Direction16(original);
		}
		public static explicit operator Direction16(Vector3 vector)
		{
			return new Direction16(vector);
		}
		public static explicit operator Direction16(Quaternion quaternion)
		{
			return quaternion * new Direction16(0, 0, 1);
		}
		public static implicit operator Vector3(Direction16 direction)
		{
			return direction.vector;
		}
		public static implicit operator Quaternion(Direction16 direction)
		{
			return Quaternion.LookRotation(direction.vector, new Direction16(direction.angles + new Vector2(-90, 0)).vector);
		}
		#endregion
		#region operators
		public static Direction16 operator *(Quaternion quaternion, Direction16 dir)
		{
			return new Direction16(quaternion * dir.vector);
		}
		public static Direction16 operator *(Direction16 dir, Quaternion quaternion)
		{
			return new Direction16(quaternion * dir.vector);
		}
		public static Direction16 operator +(Direction16 dir1, Direction16 dir2)
		{
			return new Direction16(dir1.angles + dir2.angles);
		}
		public static Direction16 operator -(Direction16 dir1, Direction16 dir2)
		{
			return new Direction16(dir1.angles - dir2.angles);
		}
		public static Direction16 operator +(Direction16 dir, Vector3 vector)
		{
			return new Direction16(dir.vector + vector);
		}
		public static Direction16 operator -(Direction16 dir, Vector3 vector)
		{
			return new Direction16(dir.vector - vector);
		}
		#endregion
		#region constructors
		
		public Direction16(Direction16 original)
		{
			_data = original._data;
		}
		public Direction16(Orientation32 original)
		{
			_data = 0;
			angles = original.angles;
		}
		public Direction16(Coordinates32 original)
		{
			_data = 0;
			angles = original.angles;
		}
		public Direction16(Coordinates64 original)
		{
			_data = 0;
			angles = original.angles;
		}
		public Direction16(Vector2 angles)
		{
			_data = 0;
			this.angles = angles;
		}
		public Direction16(Vector3 normal)
		{
			_data = 0;
			vector = normal;
		}
		public Direction16(float x, float y, float z)
				 : this(new Vector3(x, y, z))
		{
		}
		public Direction16(float xAngle, float yAngle)
				 : this(new Vector2(xAngle, yAngle))
		{
		}
		#endregion
		
		public override string ToString ()
		{
			return string.Format ("[Direction16: vector={0}, \nxAngle={1}, yAngle={2}]", vector, xAngle, yAngle);
		}
		#region properties
		#region angle properties
		public Vector2 angles
		{
			get
			{
				return new Vector2(xAngle, yAngle);
			}
			set
			{
				_data = (ushort)(ClampXAngle(value.x) * 360 + ClampYAngle(value.y));
			}
		}
		public float xAngle
		{
			get
			{
				return Mathf.Round(_data * divideBy360);
			}
			set
			{
				_data = (ushort)(ClampXAngle(value) * 360 + yAngle);
			}
		}
		public float yAngle
		{
			get
			{
				return _data - xAngle;
			}
			set
			{
				_data = (ushort)(xAngle + Mathf.RoundToInt(value));
			}
		}
		public ushort rawData
		{
			get
			{
				return _data;	
			}
			set
			{
				_data = value;
			}
		}
		#endregion
		#region vector properties
		public Vector3 vector
		{
			get
			{
				return SphereMath.ToVector(xAngle, yAngle, 1);
			}
			set
			{
				angles = (Vector2)SphereMath.ToAngles(value);
			}
		}
		public float x
		{
			get
			{
				return SphereMath.GetVectorX(xAngle, yAngle, 1);
			}
			set
			{
				angles = (Vector2)SphereMath.ToAngles(value, y, z);
			}
		}
		public float y
		{
			get
			{
				return SphereMath.GetVectorY(xAngle, yAngle, 1);
			}
			set
			{
				angles = (Vector2)SphereMath.ToAngles(x, value, z);
			}
		}
		public float z
		{
			get
			{
				return SphereMath.GetVectorZ(xAngle, yAngle, 1);
			}
			set
			{
				angles = (Vector2)SphereMath.ToAngles(x, y, value);
			}
		}
		#endregion
		#endregion
		private ushort _data;
		private const float divideBy360 = 1 / 360;
		
		private int ClampXAngle(float angle)
		{
			return (int)Mathf.Repeat(Mathf.RoundToInt(angle), 180);
		}
		private int ClampYAngle(float angle)
		{
			return (int)Mathf.Repeat(Mathf.RoundToInt(angle), 360);
		}
	}
}

